/************************************************************************
  DISCLAIMER:
  This software was produced by the National Institute of Standards
  and Technology (NIST), an agency of the U.S. government, and by statute
  is not subject to copyright in the United States.  Recipients of this
  software assume all responsibility associated with its operation,
  modification, maintenance, and subsequent redistribution.

  See NIST Administration Manual 4.09.07 b and Appendix I.
************************************************************************/

/*

This file is for creating the levelsSet.cc file. 

The levelsSet.cc file this writes has lines such as

levels aboveBelowSubs_aboveBelow_ABOVE = {2,3,0,0,0,0,0,0,0};
levels aboveBelowSubs_aboveBelow_BELOW = {3,3,0,0,0,0,0,0,0};
...
levels aclratAngularAtts_accel = {0,0,3,0,0,0,0,0,0};
...
levels xtractStmAtts_a_faLabel = {3,3,0,0,0,0,0,0,0};

Each line of levelsSet.cc defines a named level struct giving the
levels for all nine conformance modules of one attribute of a class or
one subclass of a class. The arrays are referenced by name in the
dmisConformanceTester.cc file.

In some cases (such as REPORT), attributes are allowed (in p1Lists.cc,
..., tw3Lists.cc) in p1, p2, p3, tw1, tw2, or tw3 even though any
statement that uses them in that AP level will be flagged as
non-conforming for other reasons. This is so that it will not be
decided that an addendum is required in addition to a higher level of
the AP. For example, reportStmAtts_a_rLabel is allowed in p1 and tw1
even though no instance of a REPORT statement is allowed in p1 or tw1. That
way, it is possible to REPORT a DMEID if qis1 is used with p1 or tw1,
but qis1 will not be required if a REPORT statement is used in p3 or
tw3 (and nothing in qis1 is used).

For the same reason, in some cases (such as valueStmSubs), subtypes
are allowed in p1, p2, p3, tw1, tw2, or tw3 even though any statement
that uses them in that AP level will be flagged as non-conforming for
other reasons. For example, valueStmSubs are allowed in p1 even though
no instance of a VALUE statement is allowed in p1.

Special handling is needed in four cases.

Three of the special cases are of the same sort. That is, a particular
dmisFreeStatement is not allowed in Prismatic 2 or Thin Walled 1, but
is allowed in an addendum. It is not OK to modify p2Lists.cc or
tw1Lists.cc to allow the statement because then the dmisConformanceChecker
will have it as a statement that is part of the conformance class. For
these three cases, after makeLevels runs, the levelsSet.cc file it
generates must be edited manually as follows:

levels dmisFreeStatementSubs_reportStm = {3,2,0,0,0,-1,-1,0,0};
Change to:                               {1,1,0,0,0, 0, 0,0,0};

levels dmisFreeStatementSubs_scnsetStm = {3,2,0,0,2,0,0,0,0};
Change to:                               {1,1,0,0,0,0,0,0,0};

levels dmisFreeStatementSubs_valueStm = {3,2,2,2,0,0,0,0,0};
Change to:                              {1,1,0,0,0,0,0,0,0};


The fourth special case is levels intFuncPtdataAtts_a_faLabel =
{3,3,0,0,2,0,0,0,0}; In this case, cs2 is an alternative to p3 or
tw3. This in not changed in levelsSet.cc. Rather, special handling of
intFuncPtdataAtts_a_faLabel is hard coded in
dmisConformanceTesterStart.cc and dmisConformanceTester.cc.

To do its work, this file uses four arrays (or sets of arrays) as follows.

A. levelsList

The levelsList is an array of levelData structs. levelsList is
partially populated by the makeLevelsList function, which inserts
two strings in each entry but leaves the levels array all zeros.
The levels arrays in the entries of levelsList are populated
by the process function, which is called 27 times, once for each
member of the modList. Once the levelsList is fully populated,
it has the following format.

struct levelData levelsList[] = {
{"aboveBelowSubs", "aboveBelow_ABOVE", {2,3,0,0,0,0,0,0,0}},
{"aboveBelowSubs", "aboveBelow_BELOW", {3,3,0,0,0,0,0,0,0}},
...
{"aclratAngularAtts", "accel", {0,0,3,0,0,0,0,0,0}},
...
{"xtractStmAtts", "a_faLabel", {3,3,0,0,0,0,0,0,0}},
{0, 0, {0,0,0,0,0,0,0,0,0}}
...
};

See the documentation of printLevels regarding how levelsSet.cc is printed
from the levelsList.

B. module-level arrays

This is a set of 27 arrays, one for each conformance module. Their names
are cs1Lists, cs2Lists, ... , sga3Lists.

These arrays are built by the assignModuleSubAtts function, defined in the
assignModuleSubAtts.cc file which is #included in this file. The
assignModuleSubAtts.cc file is generated by debnf2pars.

Each of these arrays describes what subclasses and attributes are allowed in
one conformance module.

C. modList

The modList is an array of the 27 module-level arrays. The modList is built
by the makeModList function and used in main.

D. masterSubAtts

MasterSubAtts is an array of lists of subclasses and attribute names. TheLists
is built by the assignMasterSubAtts function.

masterSubAtts[0] = aboveBelowSubs;
masterSubAtts[1] = aclratAngSpecSubs;
masterSubAtts[2] = aclratAngularAtts;
...
masterSubAtts[1624] = xtractStmAtts;
masterSubAtts[1625] = 0;

*/
namespace NDTU {
const char ** masterSubAtts[1650];  // about 1625 are set with real data
} // namespace NDTU

#include "allSubAtts.cc"   // all the lists of attributes and subclasses
#include <string.h>        // for strlen, strcpy, strcat
#include <stdio.h>         // for fopen, etc.
#include <stdlib.h>        // for exit
#include "cs1Lists.cc"
#include "cs2Lists.cc"
#include "cs3Lists.cc"
#include "ipv1Lists.cc"
#include "ipv2Lists.cc"
#include "ipv3Lists.cc"
#include "mc1Lists.cc"
#include "mc2Lists.cc"
#include "mc3Lists.cc"
#include "p1Lists.cc"
#include "p2Lists.cc"
#include "p3Lists.cc"
#include "qis1Lists.cc"
#include "qis2Lists.cc"
#include "qis3Lists.cc"
#include "rt1Lists.cc"
#include "rt2Lists.cc"
#include "rt3Lists.cc"
#include "sga1Lists.cc"
#include "sga2Lists.cc"
#include "sga3Lists.cc"
#include "tw1Lists.cc"
#include "tw2Lists.cc"
#include "tw3Lists.cc"
#include "unc1Lists.cc"
#include "unc2Lists.cc"
#include "unc3Lists.cc"
#include "assignModuleSubAtts.cc"   // defines the assignModuleSubAtts function
#include "assignMasterSubAtts.cc"   // defines the assignMasterSubAtts function

namespace NDTU {

#define MAXLEV 4500

struct levelData {
  const char * listName; // name of a list
  const char * aName;    // name of a list of attributes or subclasses
  int theLevels[9];      // the level in each of the 9 conformance modules
};                       //    at which the item is allowed (0,1,2, or 3)

struct levelData levelsList[MAXLEV]; // about 4300 are set with real data

void makeLevelsList();
void assignModuleSubAtts(); // defined in assignModuleSubAtts.cc, #included
void makeModLists();
void assignMasterSubAtts(); // defined in assignMasterSubAtts.cc, #included
void printLevels();
void process(int level, int m,
	     struct levelData * dataList, const char *** aList);
void resetLevels();

const char *** modLists[4][9]; //really only [3][9], but want easy indexing

} // namespace NDTU


/********************************************************************/

/* main

This works by going through the levelsList 27 times in parallel with
one of the 27 sublists of modLists and calling the process function.

*/

int main(int argc, char * argv[])
{
  int level; // first index for modLists (level)
  int m;     // second index for modLists (module)

  NDTU::assignMasterSubAtts();
  NDTU::makeLevelsList();
  NDTU::assignModuleSubAtts();
  NDTU::makeModLists();
  for (level = 1; level < 4; level++)
    {
      for (m = 0; m < 9; m++)
	{
	  NDTU::process(level, m, NDTU::levelsList, NDTU::modLists[level][m]);
	}
    }
  NDTU::resetLevels();
  NDTU::printLevels();
  return 0;
}

/********************************************************************/

namespace NDTU {

/********************************************************************/

/* makeLevelsList

Returned Value: none

Called By: main

This initializes the levelsList. The two names in each levelData are
set with their final values, but theLevels are all initialized to zero.

The list is terminated by setting the listName to 0 in the levelData
following the last meaningful entry.

*/

void makeLevelsList() /* NO ARGUMENTS */
{
  int k;                 // index for levelsList
  int n;                 // index for masterSubAtts
  int m;                 // index for masterSubAtts[n]
  const char * listName;
  const char ** sublist;
  struct levelData * data;

  k = 0;
  for (n = 0; masterSubAtts[n]; n++)
    {
      sublist = masterSubAtts[n];
      listName = sublist[0];
      for (m = 1; sublist[m]; m++)
	{
	  data = (levelsList + k);
	  data->listName = listName;
	  data->aName = sublist[m];
	  data->theLevels[0] = 0;
	  data->theLevels[1] = 0;
	  data->theLevels[2] = 0;
	  data->theLevels[3] = 0;
	  data->theLevels[4] = 0;
	  data->theLevels[5] = 0;
	  data->theLevels[6] = 0;
	  data->theLevels[7] = 0;
	  data->theLevels[8] = 0;
	  k++;
	  if (k >= MAXLEV)
	    {
	      fprintf(stderr, "bug in makeLevelsList\n");
	      exit(1);
	    }
	}
    }
  data = (levelsList + k);
  data->listName = 0;
  if (k >= MAXLEV)
    {
      fprintf(stderr, "bug in makeLevelsList\n");
      exit(1);
    }
}

/********************************************************************/

/* makeModLists

Returned Value: none

Called By: main

The first index for modLists is the level. The second index gives the
module.

*/

void makeModLists() /* NO ARGUMENTS */
{
  modLists[1][0] = p1Lists;
  modLists[1][1] = tw1Lists;
  modLists[1][2] = rt1Lists;
  modLists[1][3] = mc1Lists;
  modLists[1][4] = cs1Lists;
  modLists[1][5] = ipv1Lists;
  modLists[1][6] = qis1Lists;
  modLists[1][7] = unc1Lists;
  modLists[1][8] = sga1Lists;
  modLists[2][0] = p2Lists;
  modLists[2][1] = tw2Lists;
  modLists[2][2] = rt2Lists;
  modLists[2][3] = mc2Lists;
  modLists[2][4] = cs2Lists;
  modLists[2][5] = ipv2Lists;
  modLists[2][6] = qis2Lists;
  modLists[2][7] = unc2Lists;
  modLists[2][8] = sga2Lists;
  modLists[3][0] = p3Lists;
  modLists[3][1] = tw3Lists;
  modLists[3][2] = rt3Lists;
  modLists[3][3] = mc3Lists;
  modLists[3][4] = cs3Lists;
  modLists[3][5] = ipv3Lists;
  modLists[3][6] = qis3Lists;
  modLists[3][7] = unc3Lists;
  modLists[3][8] = sga3Lists;
}

/********************************************************************/

/* printLevels

Returned Value: none

Called By: main

This prints the levelsList to the levelsSet.cc file in a completely
straightforward manner. Each entry in the levelsList is used to print
one line of the file.

If an entry in the levelsList has the format:

{name1, name2, {1,2,3,4,5,6,7,8,9}}

then the corresponding line of levelsSet.cc is:

levels name1_name2 = {1,2,3,4,5,6,7,8,9};

*/

void printLevels() /* NO ARGUMENTS */
{
  FILE * levelsSet;
  int * levels;
  int n;

  levelsSet = fopen("source/levelsSet.cc", "w");
  fprintf(levelsSet, "namespace NDTU {\n");
  for (n = 0; levelsList[n].listName; n++)
    {
      levels = levelsList[n].theLevels;
      fprintf(levelsSet, "levels %s_%s = {%d,%d,%d,%d,%d,%d,%d,%d,%d};\n",
	      levelsList[n].listName, levelsList[n].aName,
	      levels[0], levels[1],  levels[2],  levels[3],  levels[4], 
	      levels[5],  levels[6],  levels[7],  levels[8]);
    }
  fprintf(levelsSet, "} // namespace NDTU\n");
  fclose(levelsSet);
}

/********************************************************************/

/* process

Returned Value: none

Called By: main

This goes through aList (index i) and dataLists (index n)
simultaneously. If the first string in aList[i] is the same as the
first string of dataList[n], then i is held fixed, and the subclass
or attribute names in aList[i] are matched against the second strings
of dataLists[n] as n increases. If a match is found, the number whose
index is m in the integer array in dataList[n] is updated.

For example, if lev is 2 and m is 0, this is working on prismatic2 and
aList will be p2Lists. For some i, aList[i] will be p2angleUnitSubs[],
which is {"angleUnitSubs", "angleUnit_ANGDEC", "angleUnit_ANGDMS", 0}.

The function will go through the dataList until it comes to the first
of the following 3 lines. 

{"angleUnitSubs", "angleUnit_ANGDEC", {1,1,0,0,0,0,0,0,0}},
{"angleUnitSubs", "angleUnit_ANGDMS", {0,0,0,0,0,0,0,0,0}},
{"angleUnitSubs", "angleUnit_ANGRAD", {0,0,0,0,0,0,0,0,0}},

The function will match the "angleUnitSubs" on the first line against
the same string at the beginning of p2angleUnitSubs.  Then it will:

1. match the "angleUnit_ANGDEC" on the first line against the second
string in p2angleUnitSubs. The "angleUnit_ANGDEC" in p2angleUnitSubs
indicates that the angleUnit_ANGDEC subclass of angleUnitSubs is allowed
in prismatic 2. The 1 already at in the first place on the first line
above indicates that angleUnit_ANGDEC is allowed in prismatic 1, so no
change will be made to that number.

2. match the "angleUnit_ANGDMS" on the second line against the third
string in p2angleUnitSubs. The "angleUnit_ANGDMS" in p2angleUnitSubs
indicates that the angleUnit_ANGDEC subclass of angleUnitSubs is
allowed in prismatic 2. The 0 in the first place on the second line
above indicates that angleUnit_ANGDEC is not allowed in prismatic 1,
so the 0 will be replaced by a 2.

All of the strings in p2angleUnitSubs have now been used, so i will be
incremented, and the first string in the new aList[i] will be matched
against the first string on the third line abovd. The strings will not
match, of course, so the third line above will not be processed
further.  If angleUnit_ANGRAD were permitted in prismatic 2, then
"angleUnit_ANGRAD" also would have been in p2angleUnitSubs.

The net effect of the processing just described will be to change the
second line above to:

{"angleUnitSubs", "angleUnit_ANGDMS", {2,0,0,0,0,0,0,0,0}},

This is not setting the level for p or tw to 4; that is done later in
resetLevels.

For this function to work, the ordering of aList and its sublists must
be the same as the ordering of dataList. aList and its sublists may
have gaps. dataList may not have gaps.

List matching of the general sort just described is done extensively in
the dmisConformanceTester.

*/

void process(                 /* ARGUMENTS                               */
 int lev,                     /* level (1, 2, or 3)                      */
 int m,                       /* module index (0-8)                      */
 struct levelData * dataList, /* list of all module levels               */
 const char *** aList)        /* list of items for this level and module */
{
  int n;               // index for dataList
  int i;               // index for aList
  int j;               // index for items
  const char ** items; // array of names (a sublist of aList)

  n = 0;
  for (i = 0; (items = aList[i]); i++)
    { // go through lists in aList, skipping attribute lists
      for (; dataList[n].listName; n++)
	{ // find the matching item in the dataList
	  if (strcmp(dataList[n].listName, items[0]) == 0)
	    break;
	}
      if (dataList[n].listName == 0)
	{
	  fprintf(stderr, "bug1 in process\n");
	  exit(1);
	}
      for (j = 1; items[j]; n++)
	{ // if list names differ, there's a bug
	  if (strcmp(dataList[n].listName, items[0]))
	    {
	      fprintf(stderr, "bug2 in process\n");
	      exit(1);
	    }
	  if (strcmp(dataList[n].aName, items[j]) == 0)
	    { // found a match of class names, so set level
	      if (dataList[n].theLevels[m] == 0)
		dataList[n].theLevels[m] = lev;
	      j++;
	    }
	}
    }
}

/********************************************************************/

/* resetLevels

Returned Value: none

Called By: main

1. If all nine values in a levels array are zero, that means that the
   item is not used in any AP or addendum. In this case, the levels for
   p and tw are set to 4, indicating that full DMIS is required.

2. If a levels array has a non-zero entry for either p or tw and another
   entry in the array is non-zero, then;
2a. If the other entry is greater than or equal to the p entry and the tw
   entry, the other entry is set to zero. This is to prevent an addendum
   from being judged to be needed when it is not needed.
2b. Otherwise, a line is printed to stdout, indicating a problem that
   cannot be handled automatically.

3. If a qis (index 6) and ipv (index 5) are non-zero and equal, both
   are set to the negative of their previous value. See documentation of
   adjustLevels in dmisConformanceTesterStart.cc for an explanation of
   why this is done.

Doing item 2 above makes it so that there may be cases where, for
example, p2 and cs2 are both judged to be required where p1 and cs2
would suffice. In such cases, no conformance error will be flagged if
the conformance test is run with the lower levels (p1 and cs2 in the
example). No natural example of this has been encountered. It would be
instructive to build an example.

*/
 
void resetLevels() /* NO ARGUMENTS */
{
  int * levels; // array of 9 ints
  int n;        // counter for levelsList
  int k;        // counter for

  for (n = 0; levelsList[n].listName; n++)
    {
      levels = levelsList[n].theLevels;
      for (k = 2; ((k < 9) && (levels[k] == 0)); k++);
      if (k == 9)
	{ // if addendum levels all zeros, set p and tw to 4 if zero
	  if (levels[0] == 0)
	    levels[0] = 4;
	  if (levels[1] == 0)
	    levels[1] = 4;
	}
      for (k = 2; k < 9; k++)
	{
	  if ((levels[0] || levels[1]) && levels[k])
	    { // if addendum level >= p level and tw level, set it to zero
	      if ((levels[k] >= levels[0]) && (levels[k] >= levels[1]))
		{
		  levels[k] = 0;
		}
	      else
		{ // print lines that have non-zero (p or tw) and non-zero other
		  printf("levels %s_%s = {%d,%d,%d,%d,%d,%d,%d,%d,%d};\n",
			 levelsList[n].listName, levelsList[n].aName,
			 levels[0], levels[1],  levels[2],  levels[3],
			 levels[4], levels[5],  levels[6],  levels[7],
			 levels[8]);
		  break;
		}
	    }
	}
      if (levels[6] && (levels[5] == levels[6]))
	{ // if the ipv and qis levels are identical, make them negative.
	  levels[6] = -levels[6];
	  levels[5] = -levels[5];
	}
    }
}

/********************************************************************/

} // namespace NDTU
